﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Caching;
using System.Configuration;
using MX.Core.Extensions;

namespace MX.Core.Caching.Memory
{
	/// <summary>
	/// 采用内存实现的缓存
	/// </summary>
	public class MemoryCaching : ICaching
	{
		/// <summary>
		/// 实例化默认的缓存
		/// </summary>
		public MemoryCaching ()
		{
			_cache = MemoryCache.Default;
		}

		/// <summary>
		/// 实例化某个对应名称的缓存
		/// </summary>
		/// <param name="name"></param>
		public MemoryCaching (string name)
		{
			_cache = new MemoryCache (name);
		}

		/// <summary>
		/// 缓存实例
		/// </summary>
		private MemoryCache _cache;

		/// <summary>
		/// 获取某个项
		/// </summary>
		/// <param name="key">键值</param>
		/// <returns></returns>
		public object Get (string key)
		{
			return _cache.Get (key);
		}

		/// <summary>
		/// 获取某个项
		/// </summary>
		/// <param name="key">键值</param>
		/// <returns></returns>
		public T Get<T> (string key)
		{
			return (T)_cache.Get (key);
		}

		/// <summary>
		/// 尝试获取某项数据
		/// </summary>
		/// <typeparam name="T">值类型</typeparam>
		/// <param name="key">键值</param>
		/// <param name="value">返回的值，如果不存在则返回默认值</param>
		/// <returns>是否获取成功</returns>
		public bool TryGet<T> (string key, out T value)
		{
			object t = Get (key);
			if (t == null) {
				value = default(T);
				return Common.CheckValueNull (value);
			} else {
				value = (T)t;
				return true;
			}
		}

		/// <summary>
		/// 获取一批数据
		/// </summary>
		/// <param name="keys">键值</param>
		/// <returns></returns>
		public IDictionary<string, object> GetAll (string[] keys)
		{
			return _cache.GetValues (null, keys);
		}

		/// <summary>
		/// 获取一批数据
		/// </summary>
		/// <param name="keys">键值</param>
		/// <returns></returns>
		public IDictionary<string, T> GetAll<T> (string[] keys)
		{
			var dic = _cache.GetValues (null, keys);
			return dic.ToDictionary<KeyValuePair<string, object>, string, T> (obj => {
				return obj.Key;
			}, obj => {
				return (T)obj.Value;
			});
		}

		/// <summary>
		/// 删除某个数据
		/// </summary>
		/// <param name="key">键值</param>
		/// <returns></returns>
		public bool Remove (string key)
		{
			return _cache.Remove (key) != null;
		}

		/// <summary>
		/// 删除一批数据
		/// </summary>
		/// <param name="keys">键值</param>
		/// <returns></returns>
		public void RemoveAll (IEnumerable<string> keys)
		{
			foreach (var key in keys)
				_cache.Remove (key);
		}

		/// <summary>
		/// 判断是否存在某个键值的缓存数据
		/// </summary>
		/// <param name="key">键值</param>
		/// <returns></returns>
		public bool Exists (string key)
		{
			return _cache.Contains (key);
		}

		/// <summary>
		/// 设置一个缓存项
		/// </summary>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <returns></returns>
		public void Set (string key, object data)
		{
			_cache.Set (key, data, MemoryCache.InfiniteAbsoluteExpiration);
		}

		/// <summary>
		/// 设置一个明确类型的缓存项
		/// </summary>
		/// <typeparam name="T">缓存数据的类型</typeparam>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		public void Set<T> (string key, T data)
		{
			_cache.Set (key, data, MemoryCache.InfiniteAbsoluteExpiration);
		}

		/// <summary>
		/// 设置一个缓存项并且设置绝对过期时间
		/// </summary>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
		/// <returns></returns>
		public void Set (string key, object data, DateTimeOffset absoluteExpiration)
		{
			_cache.Set (key, data, absoluteExpiration);
		}

		/// <summary>
		/// 设置一个明确类型的缓存项并且设置绝对过期时间
		/// </summary>
		/// <typeparam name="T">缓存数据的类型</typeparam>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
		public void Set<T> (string key, T data, DateTimeOffset absoluteExpiration)
		{
			_cache.Set (key, data, absoluteExpiration);
		}

		/// <summary>
		/// 设置一个与某些文件关连的缓存项,当这些文件被修改时缓存过期
		/// </summary>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
		/// <returns></returns>
		public void Set (string key, object data, params string[] dependentFiles)
		{
			var policy = new CacheItemPolicy ();
			policy.ChangeMonitors.Add (new HostFileChangeMonitor (dependentFiles));
			_cache.Set (key, data, policy);
		}

		/// <summary>
		/// 设置一个明确类型的且与某些文件关连的缓存项,当这些文件被修改时缓存过期
		/// </summary>
		/// <typeparam name="T">缓存数据的类型</typeparam>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
		/// <returns></returns>
		public void Set<T> (string key, T data, params string[] dependentFiles)
		{
			var policy = new CacheItemPolicy ();
			policy.ChangeMonitors.Add (new HostFileChangeMonitor (dependentFiles));
			_cache.Set (key, data, policy);
		}

		/// <summary>
		/// 设置一个具有过期时间并且与某些文件关连的缓存项,当过期时间已到或这些文件被修改时缓存过期
		/// </summary>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
		/// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
		/// <returns></returns>
		public void Set (string key, object data, DateTimeOffset absoluteExpiration, params string[] dependentFiles)
		{
			var policy = new CacheItemPolicy ();
			policy.AbsoluteExpiration = absoluteExpiration;
			policy.ChangeMonitors.Add (new HostFileChangeMonitor (dependentFiles));
			_cache.Set (key, data, policy);
		}

		/// <summary>
		/// 设置一个明确类型的且具有过期时间并且与某些文件关连的缓存项,当过期时间已到或这些文件被修改时缓存过期
		/// </summary>
		/// <typeparam name="T">缓存数据的类型</typeparam>
		/// <param name="key">键值</param>
		/// <param name="data">缓存数据</param>
		/// <param name="absoluteExpiration">绝对的过期时间,到此时间后缓存项自动过期</param>
		/// <param name="dependentFiles">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
		/// <returns></returns>
		public void Set<T> (string key, T data, DateTimeOffset absoluteExpiration, params string[] dependentFiles)
		{
			var policy = new CacheItemPolicy ();
			policy.AbsoluteExpiration = absoluteExpiration;
			policy.ChangeMonitors.Add (new HostFileChangeMonitor (dependentFiles));
			_cache.Set (key, data, policy);
		}

		#region GetOrAdd

		/// <summary>
		/// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">缓存项键值</param>
		/// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
		/// <returns></returns>
		public T GetOrAdd<T> (string key, Func<T> handler)
		{
			T value;
			if (TryGet<T> (key, out value)) {
				return value;
			} else {
				value = handler.Invoke ();
				if (Common.CheckValueNull (value)) {
					//存入缓存
					Set<T> (key, value);
				}
				return value;
			}
			;
		}

		/// <summary>
		/// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">缓存项键值</param>
		/// <param name="absoluteExpiration">绝对的过期时间，当时间超过此值时缓存项将自动失效</param>
		/// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
		/// <returns></returns>
		public T GetOrAdd<T> (string key, Func<T> handler, DateTimeOffset absoluteExpiration)
		{
			T value;
			if (TryGet<T> (key, out value)) {
				return value;
			} else {
				value = handler.Invoke ();
				if (Common.CheckValueNull (value)) {
					//存入缓存
					Set<T> (key, value, absoluteExpiration);
				}
				return value;
			}
			;
		}

		/// <summary>
		/// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">缓存项键值</param>
		/// <param name="files">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
		/// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
		/// <returns></returns>
		public T GetOrAdd<T> (string key, Func<T> handler, params string[] files)
		{
			T value;
			if (TryGet<T> (key, out value)) {
				return value;
			} else {
				value = handler.Invoke ();
				if (Common.CheckValueNull (value)) {
					//存入缓存
					Set<T> (key, value, files);
				}
				return value;
			}
			;
		}

		/// <summary>
		/// 返回某个缓存项,如果不存在则调用函数委托获取值,并将值存入缓存后返回
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="key">缓存项键值</param>
		/// <param name="absoluteExpiration">绝对的过期时间，当时间超过此值时缓存项将自动失效</param>
		/// <param name="files">缓存依赖的文件地址，当这些文件有变更时缓存项将自动失效</param>
		/// <param name="handler">缓存项不存在时重新获取数据的函数委托</param>
		/// <returns></returns>
		public T GetOrAdd<T> (string key, Func<T> handler, DateTimeOffset absoluteExpiration, params string[] files)
		{
			T value;
			if (TryGet<T> (key, out value)) {
				return value;
			} else {
				value = handler.Invoke ();
				if (Common.CheckValueNull (value)) {
					//存入缓存
					Set<T> (key, value, absoluteExpiration, files);
				}
				return value;
			}
			;
		}

		#endregion

		/// <summary>
		/// 增加值
		/// </summary>
		/// <param name="key">缓存项键值</param>
		/// <param name="amount">需要增加的值</param>
		/// <returns></returns>
		public long Increment (string key, uint amount)
		{
			long value = Exists (key) ? Get (key).As<long> () : 0;
			value += amount;
			Set (key, value);
			return value;
		}

		/// <summary>
		/// 减去值
		/// </summary>
		/// <param name="key">缓存项键值</param>
		/// <param name="amount">需要减去的值</param>
		/// <returns></returns>
		public long Decrement (string key, uint amount)
		{
			long value = Exists (key) ? Get (key).As<long> () : 0;
			value -= amount;
			Set (key, value);
			return value;
		}

		/// <summary>
		/// 释放资源
		/// </summary>
		public void Dispose ()
		{
			_cache.Dispose ();
		}

		#region 静态实例

		private static object _initLock = new object ();
		/// <summary>
		/// 缓存实例
		/// </summary>
		private static ICaching _cacheInstance = null;

		/// <summary>
		/// 获取默认的缓存实例
		/// </summary>
		public static ICaching Default {
			get {
				if (_cacheInstance == null) {
					lock (_initLock) {
						if (_cacheInstance == null) {
							string cacheName = ConfigurationManager.AppSettings ["MX.Cache.Memory.Name"];
							if (string.IsNullOrEmpty (cacheName)) {
								_cacheInstance = new MemoryCaching ();
							} else {
								_cacheInstance = new MemoryCaching (cacheName);
							}
						}
					}
				}
				return _cacheInstance;
			}
		}

		#endregion
	}
}
